﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using Emgu.CV;
using Emgu.CV.Structure;
using System.Drawing.Imaging;
using Emgu.CV.CvEnum;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace MAVI.TagAndRecognizeObjects.StickerDetection.source
{
    public class DetectSticker
    {

        private String stickerName;

        public String StickerName
        {
            get { return this.stickerName; }
            set { stickerName = value; }
        }

        public String Detect(Bitmap bmp)
        {

            bool stickerFound = false;
            String result;

            //Load the image from file
            Image<Bgr, Byte> img = new Image<Bgr,byte>(bmp);

            //Convert the image to grayscale and filter out the noise
            Image<Gray, Byte> gray = img.Convert<Gray, Byte>().PyrDown().PyrUp();

            //gray.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerGray" + ".jpg", ImageFormat.Jpeg);

            
            
            //Convert to B&W image by adaptive thresholding
            #region AdaptiveThresholding
            
            System.IntPtr grayPtr = gray.Ptr;

            CvInvoke.cvAdaptiveThreshold(grayPtr, grayPtr, 255, Emgu.CV.CvEnum.ADAPTIVE_THRESHOLD_TYPE.CV_ADAPTIVE_THRESH_GAUSSIAN_C,
                Emgu.CV.CvEnum.THRESH.CV_THRESH_BINARY, 19, 5);


            #endregion

            Gray cannyThreshold = new Gray(80);
            Gray cannyThresholdLinking = new Gray(120);

            Image<Gray, Byte> cannyEdges = gray.Canny(cannyThreshold, cannyThresholdLinking);

            //cannyEdges.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerCanny" + ".jpg", ImageFormat.Jpeg);

            //gray.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerThresh" + ".jpg", ImageFormat.Jpeg);

            LineSegment2D[] lines = cannyEdges.HoughLinesBinary(
                1, //Distance resolution in pixel-related units
                Math.PI / 45.0, //Angle resolution measured in radians.
                20, //threshold
                30, //min Line width
                10 //gap between lines
                )[0]; //Get the lines from the first channel

            #region Find triangles and rectangles

            //cannyEdges.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerCanny" + ".jpg", ImageFormat.Jpeg);

            List<Triangle2DF> triangleList = new List<Triangle2DF>();
            List<MCvBox2D> boxList = new List<MCvBox2D>();

            using (MemStorage storage = new MemStorage()) //allocate storage for contour approximation
                for (Contour<Point> contours = cannyEdges.FindContours(); contours != null; contours = contours.HNext)
                {
                    Contour<Point> currentContour = contours.ApproxPoly(contours.Perimeter * 0.05, storage);

                    if (contours.Area > 250) //only consider contours with area greater than 250
                    {
                        if (currentContour.Total == 3) //The contour has 3 vertices, it is a triangle
                        {
                            Point[] pts = currentContour.ToArray();
                            triangleList.Add(new Triangle2DF(
                               pts[0],
                               pts[1],
                               pts[2]
                               ));
                        }
                        else if (currentContour.Total == 4) //The contour has 4 vertices.
                        {
                            #region determine if all the angles in the contour are within the range of [80, 100] degree
                            
                            bool isRectangle = true;
                            Point[] pts = currentContour.ToArray();
                            LineSegment2D[] edges = PointCollection.PolyLine(pts, true);

                            for (int i = 0; i < edges.Length; i++)
                            {
                                double angle = Math.Abs(
                                   edges[(i + 1) % edges.Length].GetExteriorAngleDegree(edges[i]));
                                if (angle < 80 || angle > 100)
                                {
                                    isRectangle = false;
                                    break;
                                }
                            }
                            #endregion



                            if (isRectangle) boxList.Add(currentContour.GetMinAreaRect());
                        }
                    }
                }
            
            #endregion

            #region FindSticker(Triangle in Rectangle)

            List<Triangle2DF> triangleList2 = new List<Triangle2DF>();
            List<MCvBox2D> boxList2 = new List<MCvBox2D>();

            Triangle2DF tr = new Triangle2DF();           

            foreach (MCvBox2D box in boxList)
            {

                PointF[] pointsRect = box.GetVertices();

                
                             
                foreach (Triangle2DF triangle in triangleList)
                {

                    if (TriangleInRectangle(pointsRect, triangle))
                    {

                        if (triangle.Area > tr.Area)
                        {
                            tr = triangle;
                        }
                        boxList2.Add(box);

                    }
                }

                if (boxList2.Count != 0)
                {
                    triangleList2.Add(tr);
                    stickerFound = true;
                    break;
                }

            }

            #endregion

            if (stickerFound)
            {

                #region StickerExtraction

                try
                {
                    double rotation = boxList2.ElementAt(0).angle;

                    //extracting the Region Of Interest in the image where the sticker is situated so that the center of the image will be the center of the sticker 
                    gray.ROI = new Rectangle((int)(boxList2.ElementAt(0).center.X - 20 - Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) / 2),
                        (int)(boxList2.ElementAt(0).center.Y - 20 - Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) / 2),
                        (int)Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) + 25,
                        (int)Math.Sqrt(boxList2.ElementAt(0).size.Height * boxList2.ElementAt(0).size.Height
                        + boxList2.ElementAt(0).size.Width * boxList2.ElementAt(0).size.Width) + 25);

                    Image<Gray, Byte> imageToPaste = new Image<Gray, byte>(gray.Size);

                    CvInvoke.cvCopy(gray, imageToPaste, IntPtr.Zero);

                    //imageToPaste.ToBitmap().Save("D:\\ImagineCup\\Images\\Stickere\\" + "stickerFinal2" + ".jpg", ImageFormat.Jpeg);

                    double centerX, centerY;

                    centerX = imageToPaste.Height / 2;
                    centerY = imageToPaste.Width / 2;


                    //rotates the image so that the sticker will be in a default position and we can extract it from the image
                    imageToPaste = imageToPaste.Rotate(-rotation, new Gray(255));

                    imageToPaste.ROI = new Rectangle((int)(centerX - boxList2.ElementAt(0).size.Height / 2 - 20),
                                                     (int)(centerY - boxList2.ElementAt(0).size.Height / 2 - 20),
                                                     (int)(boxList2.ElementAt(0).size.Height + 40),
                                                     (int)(boxList2.ElementAt(0).size.Height + 40));


                    Image<Gray, Byte> finalSticker = new Image<Gray, Byte>(imageToPaste.Size);

                    CvInvoke.cvCopy(imageToPaste, finalSticker, IntPtr.Zero);

                    finalSticker = finalSticker.Resize(200, 200, INTER.CV_INTER_AREA);

                    
                    #region Write the Sticker in the XML file

                    if (!File.Exists("D:/ImagineCup/Stickers/stickers.xml"))
                    {

                        XmlTextWriter textWritter = new XmlTextWriter("D:/ImagineCup/Stickers/stickers.xml", null);

                        textWritter.WriteStartDocument();

                        textWritter.WriteStartElement("stickers");

                        textWritter.WriteEndElement();

                        textWritter.Close();

                    }

                    XmlDocument xmlDoc = new XmlDocument();

                    xmlDoc.Load("D:/ImagineCup/Stickers/stickers.xml");

                    XmlElement subRoot = xmlDoc.CreateElement("sticker");

                    //stickerName
                    XmlElement appendedElementStickerName = xmlDoc.CreateElement("stickerName");

                    XmlText xmlTextStickerName = xmlDoc.CreateTextNode(this.stickerName);

                    appendedElementStickerName.AppendChild(xmlTextStickerName);

                    subRoot.AppendChild(appendedElementStickerName);

                    xmlDoc.DocumentElement.AppendChild(subRoot);

                    //stickerPath
                    XmlElement appendedElementStickerPath = xmlDoc.CreateElement("stickerPath");

                    XmlText xmlTextStickerPath = xmlDoc.CreateTextNode("D:\\ImagineCup\\Stickers\\" + this.stickerName + ".jpg");

                    appendedElementStickerPath.AppendChild(xmlTextStickerPath);

                    subRoot.AppendChild(appendedElementStickerPath);

                    xmlDoc.DocumentElement.AppendChild(subRoot);

                    xmlDoc.Save("D:/ImagineCup/Stickers/stickers.xml");

                    #endregion
                    
                    finalSticker.ToBitmap().Save("D:\\ImagineCup\\Stickers\\" + this.stickerName + ".jpg", ImageFormat.Jpeg);

                    result = stickerName;
                    
                }
                catch (Exception e)
                {
                    result = "not found";
                }
                #endregion
            }
            else
            {
                result = "not found";
            }

            return result;
        }

        private bool TriangleInRectangle(PointF[] pointsRect, Triangle2DF triangle)
        {

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V0) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V0) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V0) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V0) < 0)
            {
                return false;
            }

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V1) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V1) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V1) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V1) < 0)
            {
                return false;
            }

            if (TriangleArea(pointsRect[0], pointsRect[1], triangle.V2) < 0 || TriangleArea(pointsRect[1], pointsRect[2], triangle.V2) < 0 || TriangleArea(pointsRect[2], pointsRect[3], triangle.V1) < 0 || TriangleArea(pointsRect[3], pointsRect[0], triangle.V2) < 0)
            {
                return false;
            }
            return true;
        }

        private double TriangleArea(PointF point1, PointF point2, PointF point3)
        {

            double area = 0.5 * (point2.X * point3.Y - point2.Y * point3.X - point1.X * point3.Y + point1.Y * point3.X + point1.X * point2.Y - point1.Y * point2.X);

            return area;

        }
    }
}